iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0
SideProject30

用 Rails 打造你的電商網站系列 第 12

Day 12 為會員做權限管理

  • 分享至 

  • xImage
  •  

我們在前幾天已經把前後台區分開來了

不過如果後台的路徑被使用者猜到,那也是很頭痛的事情

所以我們就需要做權限管理

Pundit 可以幫我們達到這個效果

Pundit 是什麼

Pundit 可以讓我們輕鬆達成權限管理,
我們可以透過 policy 來決定使用者所能接觸到的網頁或範圍,
而 scope 則可以幫我們依照使用者的位階或者身份來分出顯示資訊。

Pundit 該怎麼用

step 1. 安裝 Pundit

gem 'pundit'

step 2. 引入 pundit 模組

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  include Pundit::Authorization
end

step 3. 產生 policy

會自動作出 application_policy.rb 檔案,可以把它想像成是 application_controller ,為最上層的概念

Policy 代表政策的意思,不管是在現實世界還是在網路世界,我們必須都要有政策,並且規範所有人的權利

因此我們也需要在網站中明定,誰有權利存取特定功能

其中的 application_policy 就像是中華民國的憲法一樣,為最高的政策

> rails g pundit:install

create  app/policies/application_policy.rb

我們先來看看 application_policy 這個檔案

一開始初始化的時候,
就會把 current_user 以 user 帶入,
而 record 則是每個 model

class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @user = user
    @record = record
  end

  ...

  class Scope
    ...
  end
end

接下來我們來看一下 Policy 裡面的每個方法

false 表示每個人都可以看到

而有幾個比較特別的是 new 方法裡面有 create? 這表示 new 的權限應該要跟 create 一樣

但為什麼不是 create 中包著 new? 方法呢?主要是因為能 create 的人就一定能到 new 的頁面,不能 create 的人就不應該進入 new 頁面

# app/policies/application_policy.rb

  def index?
    false
  end
  
  def new?
    create?
  end

我們再來看到 Scope,

當我們需要設定特定的權限或者方法時,可以把它放在 Scope 中

初始化的時候會去呼叫 current_user ,作為 user

scope 則可能是 model 或者是關聯性 model

每個 policy class 都會有 Scope

class Scope
  def initialize(user, scope)
    @user = user
    @scope = scope
  end

  def resolve
    raise NotImplementedError, "You must define #resolve in #{self.class}"
  end

  private

  attr_reader :user, :scope
end

step 4. 如果沒有權限,該怎麼回應

當使用者不小心進到沒有權限的網頁,會噴錯誤訊息,
但我們總不可能讓使用者看到錯誤訊息吧?應該是要讓他看到沒有權限的頁面

我們需要去拯救 Pundit 的 NotAuthorizeError ,使用 rescue_from 來接住,並且提供回應的方法

而我們希望能回給他一個 403 頁面,告訴他你沒有足夠的權限

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized
    render file: "#{Rails.root}/public/403.html",
           layout: false,
           status: :forbidden
  end
end

step 5. 設定 Pundit 權限

因為要設定的權限有點多,我們就只拿後台的 drinks index 頁面來說
後台只有 商店的員工、商店老闆以及管理員看得到

為了方便管理,我把後台的 policy 檔案整理到 admin 資料夾中,所以必須加上 module Admin

我們在 index? 方法就需要去判斷 current_user 是不是以上角色

不是的話就會 raise Pundit::NotAuthorizedError,並且依照我們上一步設定的,呈現 403 無權限的畫面給他

# app/policies/admin/drink_policy.rb

module Admin
  class DrinkPolicy < ApplicationPolicy
    attr_reader :user, :record

    def initialize(user, record)
      @user = user
      @record = record
    end

    def index?
      user.employee? || user.owner? || user.admin?
    end
  
    ...
  
    class Scope
      ...
    end
  end
end

step 6. 在 controller 加入 pundit

由於我們的 policy 是 namespace ,

所以必須要用 [:admin, :drink] 他才知道要去找 Admin::DrinkPolicy

我們就會在 DrinkPolices 的 index? 方法中判斷目前的使用者角色,是否可以存取這個 record

module Admin
  class DrinksController < AdminController
    ...

    def index
      authorize([:admin, :drink])
      @pagy, @drinks = pagy(Drink.with_deleted)
    end

    ...
  end
end

目前權限設定就差不多了,其他的方法也跟著照做應該就行囉

Scope

我們目前還用不到 Scope

Scope 主要是用在當今天的資料需要依照身份的不同而呈現不同才需要

例如,今天總經理需要看到所有店家的營業額,一般員工只需要看到他們自己店家的營業額時,我們才會用到 Scope

如果有使用到的話,也可以參考官方手冊,裡面寫的都蠻詳細的

以上是我們今天的權限設定~


上一篇
Day 11 茫茫商品中一鍵認出你
下一篇
Day 13 美化你的商品網址
系列文
用 Rails 打造你的電商網站30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言